﻿
#region 文件信息
/*----------------------------------------------------------------
// 
// 文件名称：	
// 文件功能描述：	
// 设计要求：	
//
// 文 件 名：    ISubMessageService
// 创建者：      杨程
// 创建日期：	    2022/11/12 13:50:48

//----------------------------------------------------------------*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

//

namespace Vampirewal.Core.SubMessageService
{
    /// <summary>
    /// 消息抽象类，用于定义消息类型，具体的消息需要继承该类
    /// </summary>
    public abstract class SubMessage
    {
        /// <summary>
        /// 
        /// </summary>
        protected SubMessage(object sender)
        {
            //构造函数
            this.Sender = sender ?? throw new ArgumentNullException(nameof(sender));
        }

        public object Sender { get; set; }
    }


    /// <summary>
    /// 
    /// </summary>
    public interface ISubMessageService
    {
        /// <summary>
        /// 订阅消息
        /// </summary>
        /// <typeparam name="TMessage">继承自SubMessage的消息类</typeparam>
        /// <param name="recipient"></param>
        /// <param name="action"></param>
        /// <param name="threadOption"></param>
        void Subscribe<TMessage>(object recipient, Action<TMessage> action,
        ThreadOption threadOption = ThreadOption.PublisherThread) where TMessage : SubMessage;

        /// <summary>
        /// 取消订阅
        /// </summary>
        /// <typeparam name="TMessage"></typeparam>
        /// <param name="recipient"></param>
        /// <param name="action"></param>
        void Unsubscribe<TMessage>(object recipient, Action<TMessage>? action = null) where TMessage : SubMessage;

        /// <summary>
        /// 消息推送
        /// </summary>
        /// <typeparam name="TMessage"></typeparam>
        /// <param name="sender"></param>
        /// <param name="message"></param>
        void Publish<TMessage>(object sender, TMessage message) where TMessage : SubMessage;
    }

    public enum ThreadOption
    {
        PublisherThread,
        BackgroundThread,
        UiThread
    }

    public class VampirewalSubMessageService : ISubMessageService
    {
        public static readonly VampirewalSubMessageService Default = new VampirewalSubMessageService();
        private readonly object registerLock = new object();

        private Dictionary<Type, List<WeakActionAndToken>>? recipientsOfSubclassesAction;

        public void Subscribe<TMessage>(object recipient, Action<TMessage> action, ThreadOption threadOption)
            where TMessage : SubMessage
        {
            lock (this.registerLock)
            {
                var messageType = typeof(TMessage);

                this.recipientsOfSubclassesAction ??= new Dictionary<Type, List<WeakActionAndToken>>();

                List<WeakActionAndToken> list;

                if (!this.recipientsOfSubclassesAction.ContainsKey(messageType))
                {
                    list = new List<WeakActionAndToken>();
                    this.recipientsOfSubclassesAction.Add(messageType, list);
                }
                else
                {
                    list = this.recipientsOfSubclassesAction[messageType];
                }

                var item = new WeakActionAndToken
                { 
                    Recipient = recipient, 
                    ThreadOption = threadOption, 
                    Action = action 
                };

                list.Add(item);
            }
        }

        public void Unsubscribe<TMessage>(object? recipient, Action<TMessage>? action) where TMessage : SubMessage
        {
            var messageType = typeof(TMessage);

            if (recipient == null || this.recipientsOfSubclassesAction == null ||
                this.recipientsOfSubclassesAction.Count == 0 || !this.recipientsOfSubclassesAction.ContainsKey(messageType))
            {
                return;
            }

            var lstActions = this.recipientsOfSubclassesAction[messageType];
            for (var i = lstActions.Count - 1; i >= 0; i--)
            {
                var item = lstActions[i];
                var pastAction = item.Action;

                if (pastAction != null
                    && recipient == pastAction.Target
                    && (action == null || action.Method.Name == pastAction.Method.Name))
                {
                    lstActions.Remove(item);
                }
            }
        }

        public void Publish<TMessage>(object sender, TMessage message) where TMessage : SubMessage
        {
            var messageType = typeof(TMessage);

            if (this.recipientsOfSubclassesAction != null)
            {
                var listClone = this.recipientsOfSubclassesAction.Keys.Take(this.recipientsOfSubclassesAction.Count)
                    .ToList();

                foreach (var type in listClone)
                {
                    List<WeakActionAndToken>? list = null;

                    if (messageType == type || messageType.IsSubclassOf(type) || type.IsAssignableFrom(messageType))
                    {
                        list = this.recipientsOfSubclassesAction[type]
                            .Take(this.recipientsOfSubclassesAction[type].Count)
                            .ToList();
                    }

                    if (list is { Count: > 0 })
                    {
                        this.SendToList(message, list);
                    }
                }
            }
        }

        private void SendToList<TMessage>(TMessage message, IEnumerable<WeakActionAndToken> weakActionsAndTokens)
            where TMessage : SubMessage
        {
            var list = weakActionsAndTokens.ToList();
            var listClone = list.Take(list.Count()).ToList();

            foreach (var item in listClone)
            {
                if (item.Action is { Target: { } })
                {
                    switch (item.ThreadOption)
                    {
                        case ThreadOption.BackgroundThread:
                            Task.Run(() => { item.ExecuteWithObject(message); });
                            break;
                        case ThreadOption.UiThread:
                            SynchronizationContext.Current!.Post(_ => { item.ExecuteWithObject(message); }, null);
                            break;
                        default:
                            item.ExecuteWithObject(message);
                            break;
                    }
                }
            }
        }
    }

    public class WeakActionAndToken
    {
        public object? Recipient { get; set; }

        public ThreadOption ThreadOption { get; set; }

        public Delegate? Action { get; set; }

        public string? Tag { get; set; }

        public void ExecuteWithObject<TMessage>(TMessage message) where TMessage : SubMessage
        {
            if (this.Action is Action<TMessage> factAction)
            {
                factAction.Invoke(message);
            }
        }
    }
}
